package org.jvm.instruction.references.invokemethod;

import org.jvm.instruction.base.*;
import org.jvm.rtda.Object;
import org.jvm.rtda.heap.*;
import org.jvm.rtda.heap.classmember.Method;
import org.jvm.rtda.heap.symref.MethodRef;
import org.jvm.rtda.thread.Frame;

/**
 * 实例方法调用
 *
 * @author 海燕
 * @date 2023/2/7
 */
public class INVOKE_VIRTUAL extends Index16Instruction {

    @Override
    public void execute(Frame frame) {
        MethodRef methodRef = (MethodRef) frame.getMethod().getKlass().getConstantPool().getConstant(this.index);
        //被调用方法编译期类型（可能是抽象方法）
        Method resolvedMethod = methodRef.resolveMethodRef(frame.getThread());

        //被调用方法实际所在类
        Klass infactClass = resolvedMethod.getKlass();
        //调用方，指令实际执行所在类
        Klass currentClass = frame.getMethod().getKlass();
        //实例的构造方法必须由实例本类发起调用
        if (resolvedMethod.getName().equals("<init>") && infactClass != methodRef.resolvedClass(frame.getThread())) {
            throw new RuntimeException("init method error");
        }
        //实例方法不应是静态
        if (resolvedMethod.isStatic()) {
            throw new RuntimeException("method is static");
        }
        //获取实例引用
        Object ref = frame.getOperandStack().getRefFromTop(resolvedMethod.getArgSlotCount() - 1);
        if (ref == null) {
            frame.getThread().throwNullPointerException();
            return;
        }
        //参考INVOKE_SPECIAL底部注释
        if (resolvedMethod.isProtected()
                && infactClass.isSuperClassOf(currentClass)
                && !currentClass.getPackageName().equals(infactClass.getPackageName())
                && ref.getKlass() != currentClass
                && !ref.getKlass().isSubClassOf(currentClass)
                //数组类的clone方法不校验
                && !(ref.getKlass().isArray() && resolvedMethod.getName().equals("clone"))) {
            throw new RuntimeException("method is protected");
        }
        //从实例中查找实际要执行的方法
        Method methodToBeInvoked = MethodUtil.lookupMethodInClass(ref.getKlass(), methodRef.getName(), methodRef.getDescriptor());
        //不能是抽象方法
        if (methodToBeInvoked == null || methodToBeInvoked.isAbstract()) {
            throw new RuntimeException("method is abstract or null");
        }
        //开始方法调用
        InstructionUtil.invokeMethod(frame, methodToBeInvoked);
    }
}
